#pragma once

#include <GL/glew.h>
#include "../Core/DCoordinates3.h"
#include "../Core/Matrices.h"
#include "../Core/GenericCurves3.h"
#include "../B-spline/BSplineCurves3.h"

namespace cagd
{
    class ParametricCurve3
    {
    public:
        // function pointer to the zeroth or higher order derivative of a parametric curve
        typedef DCoordinate3 (*Derivative)(GLdouble);

    protected:
        GLdouble                    _u_min, _u_max; // definition domain
        GLboolean                   _is_closed;     // GL_TRUE in case of a periodic curve, GL_FALSE in case of open curves
        ColumnMatrix<Derivative>    _derivative;    // function pointers of zeroth and possible higher order derivatives

    public:
        BSplineCurve3*              bspc;

        // homework: special constructor
        ParametricCurve3(GLdouble u_min, GLdouble u_max, GLboolean is_closed, const ColumnMatrix<Derivative> &derivative);

        // destructor
        ~ParametricCurve3();

        // homework: calculate the energy at a given curve point
        GLdouble Energy(GLdouble u, GLdouble w_l, GLdouble lambda, GLdouble w_c, GLdouble mu) const;

        // homework: calculate the total energy along the curve
        GLdouble TotalEnergy(GLdouble w_l, GLdouble lambda, GLdouble w_c, GLdouble mu) const;

        // homework: evaluate the given order derivative at the parameter value u
        DCoordinate3 operator ()(GLuint order, GLdouble u) const;

        // homework: using the user defined function pointers generate the image and derivatives of the given parametric curve
        GenericCurve3* GenerateImage(GLuint div_point_count, GLenum usage_flag = GL_STATIC_DRAW) const;

        // homework:
        //
        // using the studied least squares algorithm and multi-threading (QtConcurrent::blockingMap),
        // approximate the shape of the given parametric curve with a B-spline curve
        //
        // data_usage_flag is
        //
        // if the parametric curve is periodic (open) then you must use a periodic (clamped) knot vector
        BSplineCurve3* ConvertToBSplineCurve(
                GLuint p,                               // order of the B-spline curve
                GLuint n,                               // n + 1 denotes the number of unrepeated control points
                GLdouble w_l,                           // energy parameter
                GLdouble lambda,                        // energy parameter
                GLdouble w_c,                           // energy parameter
                GLdouble mu,                            // energy parameter
                GLuint integral_div_points,             // subdivision points needed by the Simpson quadrature formula
                GLdouble &error,                        // denotes the distance between the given parametric curve and the approximating B-spline curve
                GLenum data_usage_flag = GL_STATIC_DRAW //an input parameter of the BSplineCurve3's special constructor
             );

        GLdouble Length(GLuint div_point_count) const;
        unsigned long long nchoosek(unsigned long long n, unsigned long long k);
        RowMatrix<GLdouble> prodRowMatrix(RowMatrix<GLdouble> a, RowMatrix<GLdouble> b);
        RowMatrix<GLdouble> A(RowMatrix<GLdouble> u, GLint n, GLint i);
        RowMatrix<GLdouble> TA(RowMatrix<GLdouble> u, GLint n, GLint i);
        RowMatrix<GLdouble> linspace(GLdouble min, GLdouble max, int n);
        RowMatrix<GLdouble> f(RowMatrix<GLdouble> v, GLuint n);

    };
}
